//
//  Network.swift
//  HinsLive
//
//  Created by haoxian on 2019/4/19.
//  Copyright © 2019 Hins. All rights reserved.
//

import Foundation
import Moya
import SwiftProtobuf
import RxSwift
import RxSwiftExt
import Reachability

typealias NetworkModel = SwiftProtobuf.Message

final class Network {
    
    private static let provider = MoyaProvider<NetworkTarget>()
    
    static let reachability = Reachability.forInternetConnection()!
    
    static let scheduler = ConcurrentDispatchQueueScheduler(qos: .default)
    
    static var host: String {
        return "http://182.254.167.62"
    }
    
    static var rtmpHost: String {
        return "rtmp://182.254.167.62/live/"
    }
    
    static var socketHost: String {
        return "http://182.254.167.62/socket.io"
    }
    
    static var isReachable: Bool {
        return reachability.isReachable()
    }
    
    static func startNetworkChangeNotification() {
        reachability.startNotifier()
    }
    
    static func stopNetworkChangeNotification() {
        reachability.stopNotifier()
    }
    
    #if DEBUG
    static var isMock: Bool = false
    #endif
    
    static func request<T: NetworkModel>(_ type: T.Type, _ target: NetworkTarget) -> Observable<Event<T>> {
        guard isReachable else { return .just(Event.error(NetworkError.networkDisConnected)) }
        #if DEBUG
        return isMock ? provider.mock(type, target) : provider.request(type, target)
        #else
        return provider.request(type, target)
        #endif
    }
}

private extension MoyaProvider where Target == NetworkTarget {
    
    func request<T: NetworkModel>(_ type: T.Type, _ target: Target) -> Observable<Event<T>> {
        return rx.request(target)
            .retry(3)
            .timeout(5.0, scheduler: Network.scheduler)
            .mapModel(T.self)
            .asObservable()
            .materialize()
    }
    
    func mock<T: NetworkModel>(_ type: T.Type, _ target: Target) -> Observable<Event<T>> {
        return Single.just(Moya.Response(statusCode: 200, data: target.sampleData), scheduler: Network.scheduler)
            .delay(0.2, scheduler: Network.scheduler)
            .mapModel(T.self)
            .asObservable()
            .materialize()
    }
}


private extension PrimitiveSequenceType where Self.TraitType == RxSwift.SingleTrait, Self.ElementType == Moya.Response {
    func mapPBResponse() -> Single<PBResponse> {
        return flatMap { resp -> Single<PBResponse> in
            guard resp.statusCode < 500 else {
                return .error(NetworkError.serverError)
            }
            guard let pbResp = try? PBResponse(serializedData: resp.data) else {
                return .error(NetworkError.parseDataError)
            }
            return .just(pbResp)
        }
    }
    
    func mapModel<T: NetworkModel>(_ type: T.Type) -> Single<T> {
        return mapPBResponse().mapModel(T.self)
    }
}

private extension PrimitiveSequenceType where Self.TraitType == RxSwift.SingleTrait, Self.ElementType == PBResponse {
    func mapModel<T: NetworkModel>(_ type: T.Type) -> Single<T> {
        return flatMap { resp -> Single<T> in
            if resp.result {
                if let model = try? T(serializedData: resp.data) {
                    return .just(model)
                } else if resp.data.count > 0 {
                    return .error(NetworkError.parseDataError)
                } else {
                    return .error(NetworkError.responseDataInvalid)
                }
            }
            if let _ = resp.error {
                return .error(NetworkError.errorResponse(resp.errorMsg))
            }
            return .error(NetworkError.responseDataInvalid)
        }
    }
}
